package updown.service.jar;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.LineHandler;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.ssh.ChannelType;
import cn.hutool.extra.ssh.JschUtil;
import cn.hutool.extra.ssh.Sftp;
import com.jcraft.jsch.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import updown.config.CommonConfig;
import updown.domain.ServerHost;
import updown.util.Constant;

import java.io.*;
import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author bwcx_jzy
 * @date 2019/8/9
 */
@Slf4j
@Service
public class SshUtilService {

    @Autowired
    private CommonConfig commonConfig;

    public static Session getSession(ServerHost serverHost) {
        Session session = JschUtil.openSession(serverHost.getHost(), serverHost.getPort(), serverHost.getUser(), serverHost.getPassword());
        try {
            session.setServerAliveInterval((int) TimeUnit.SECONDS.toMillis(50));
        } catch (JSchException e) {
            e.printStackTrace();
            log.info(" getSession(ServerHost serverHost)：{} - error:{}", serverHost, e);
        }
        session.setServerAliveCountMax(5);
        return session;

    }


    /**
     * 执行命令
     *
     * @param serverHost ssh
     * @param command    命令 多条命令用 && 拼接
     * @return 结果
     * @throws IOException   io
     * @throws JSchException jsch
     */
    public String[] execShell(ServerHost serverHost, String[] commands) throws IOException, JSchException {
        Session session = null;
        ChannelShell channel = null;
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            session = getSession(serverHost);
            channel = (ChannelShell) JschUtil.createChannel(session, ChannelType.SHELL);
            // 添加环境变量
            inputStream = channel.getInputStream();//从远端到达的数据  都能从这个流读取到
            channel.setPty(true);
            channel.connect();
            outputStream = channel.getOutputStream();//写入该流的数据  都将发送到远程端
            //使用PrintWriter 就是为了使用println 这个方法
            PrintWriter printWriter = new PrintWriter(outputStream);
            for (String command : commands) {
                printWriter.println(command);
            }
//            printWriter.println("cd /opt/app");
//            printWriter.println("exit");//为了结束本次交互
            printWriter.flush();//把缓冲区的数据强行输出

            /**
             shell管道本身就是交互模式的。要想停止，有两种方式：
             一、人为的发送一个exit命令，告诉程序本次交互结束
             二、使用字节流中的available方法，来获取数据的总大小，然后循环去读。
             为了避免阻塞
             */
            StringBuffer sb = new StringBuffer();
            byte[] tmp = new byte[4096];
            while (true) {
                while (inputStream.available() > 0) {
                    int i = inputStream.read(tmp, 0, 4096);
                    if (i < 0) break;
                    String s = new String(tmp, 0, i);
                    if (s.indexOf("--More--") >= 0) {
                        outputStream.write((" ").getBytes());
                        outputStream.flush();
                    }
                    log.info("重启脚本 中间结果:{}", s);
                    sb.append(s);
                }
                if (channel.isClosed()) {
                    log.info("重启脚本 exit-status :{}",  channel.isClosed());
                    break;
                }
            }
            log.info("执行 重启脚本 执行完成 ");
            String[] res = new String[2];
            res[0] = sb.toString();
            String resStr = sb.toString();
            boolean has = resStr.contains("Permission denied");
            if (has) {
                log.info("有Permission denied,错误信息{}",resStr);
                res[1] = resStr;
            }
            log.info("成功信息 :{}",sb.toString());
            return res;
        } finally {
            JschUtil.close(session);
            JschUtil.close(channel);
            outputStream.close();
            inputStream.close();
        }
    }

    /**
     * 执行命令
     *
     * @param serverHost ssh
     * @param command    命令 多条命令用 && 拼接
     * @return 结果
     * @throws IOException   io
     * @throws JSchException jsch
     */
    public String[] exec(ServerHost serverHost, String command) throws IOException, JSchException {
        Session session = null;
        try {
            session = getSession(serverHost);
            return exec(session, serverHost.getCharset(), command);
        } finally {
            JschUtil.close(session);
        }
    }

    /**
     * 执行名称
     *
     * @param session
     * @param charset
     * @param command
     * @return
     * @throws IOException
     * @throws JSchException
     */
    private String[] exec(Session session, Charset charset, String command) throws IOException, JSchException {
        ChannelExec channel = null;
        try {
            channel = (ChannelExec) JschUtil.createChannel(session, ChannelType.EXEC);
            // 添加环境变量
            channel.setCommand(command);
            InputStream inputStream = channel.getInputStream();
            InputStream errStream = channel.getErrStream();
            channel.connect();
            // 读取结果
            String result = IoUtil.read(inputStream, charset);
            //
            String error = IoUtil.read(errStream, charset);
            String[] res = new String[2];
            res[0] = result;
            res[1] = error;
            log.info("执行 exec 的正常返回信息:{}",res[0]);
            log.info("执行 exec 的error信息:{}",res[1]);
            return res;
        } finally {
            JschUtil.close(channel);
        }
    }

    /**
     * 上传文件 ： 只能保证和本地的文件名路径都是一致的
     *
     * @param serverHost ssh
     * @param remotePath 远程路径
     * @param desc       文件夹或者文件
     */
    public void uploadDirWithSamePathAndName(ServerHost serverHost, String remotePath, File desc) {
        Session session = null;
        ChannelSftp channel = null;
        try {
            session = getSession(serverHost);
            channel = (ChannelSftp) JschUtil.openChannel(session, ChannelType.SFTP);
            Sftp sftp = new Sftp(channel, serverHost.getCharset());
            sftp.syncUpload(desc, remotePath);
            //uploadDir(channel, remotePath, desc, serverHost.getCharset());
        } finally {
            JschUtil.close(channel);
            JschUtil.close(session);
        }
    }

    /**
     * 上传文件  commonConfig.getJarPath()  默认上传到 /opt/app 目录下， 文件名 就是 本地文件名，截取最后的 "-" 之后
     * yq-auth-version1.jar => yq-auth.jar
     *
     * @param serverHost ssh
     * @param localPath  文件夹或者文件
     */
    public String uploadAndOverwriteByServiceName(ServerHost serverHost, String dirPath, String localPath) {
        Session session = null;
        ChannelSftp channel = null;
        try {
            session = getSession(serverHost);
            channel = (ChannelSftp) JschUtil.openChannel(session, ChannelType.SFTP);
            try {
                log.info("log.info:channel 开始传输文件， 本地的文件路径是 >>>:{}", localPath);
                File file = FileUtil.file(localPath);
                // usualName
                String fileName = file.getName();
                int lastIndexOf = fileName.lastIndexOf("-");
                String usualFileName = fileName.subSequence(0, lastIndexOf).toString() + ".jar";
                log.info("usualFileName:{}", usualFileName);

                try {
                    channel.rm(dirPath + "/" + usualFileName);
                    log.info("删除远程文件成功");
                } catch (SftpException e) {
                    log.info("在删除的时候：{}", e);
                }

                channel.cd(dirPath);
                channel.put(IoUtil.toStream(file), usualFileName);
                log.info("发送文件成功");
            } catch (Exception e) {
                log.info("log.info:uploadFile 失败 >>>:{}", e);
                return Constant.fail;
            } finally {
                JschUtil.close(channel);
                JschUtil.close(session);
            }
            return Constant.success;
        } finally {
            JschUtil.close(channel);
            JschUtil.close(session);
        }
    }


    /**
     * 检查是否存在正在运行的进程
     *
     * @param serverHost ssh
     * @param tag        标识
     * @return true 存在运行中的
     * @throws IOException   IO
     * @throws JSchException jsch
     */
    public boolean checkSshRun(ServerHost serverHost, String tag) throws IOException, JSchException {
        String ps = StrUtil.format("ps -ef | grep -v 'grep' | egrep {}", tag);
        Session session = null;
        ChannelExec channel = null;
        try {
            session = getSession(serverHost);
            channel = (ChannelExec) JschUtil.createChannel(session, ChannelType.EXEC);
            channel.setCommand(ps);
            InputStream inputStream = channel.getInputStream();
            InputStream errStream = channel.getErrStream();
            channel.connect();
            Charset charset = serverHost.getCharset();
            // 运行中
            AtomicBoolean run = new AtomicBoolean(false);
            IoUtil.readLines(inputStream, charset, (LineHandler) s -> {
                run.set(true);
            });
            if (run.get()) {
                return true;
            }
            run.set(false);
            AtomicReference<String> error = new AtomicReference<>();
            IoUtil.readLines(errStream, charset, (LineHandler) s -> {
                run.set(true);
                error.set(s);
            });
            if (run.get()) {
                throw new RuntimeException("检查异常:" + error.get());
            }
        } finally {
            JschUtil.close(channel);
            JschUtil.close(session);
        }
        return false;
    }
}
